#!/usr/bin/python
# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
This file contain some useful controller classes to be used with the interface.

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


import sys

# GTK+ imports
import pygtk
if not sys.platform == 'win32':
    pygtk.require('2.0')
try:
    import gtk
    import gtk.gdk
except:
    log.error("gtk or gobject or gtk.gdk module not found!")
    sys.exit(1)

# Project imports
from log import log
log.debug("Loaded...")


class PanController(object):
    '''This class defines a pan function of the click-and-drag type. It allows
       the definition of reference surface, that must send the mouse events and
       two Adjustments (vertical and horizontal), that will be set according to
       the drag done in the reference surface. It is possible to apply scaling
       in the movement, so that a large surface can be controlled by a small
       area (and vice-versa).'''
    
    STATE_OFF = 1
    STATE_ON = 2
       
    def __init__(self, panButton=1):
        
        self.state = self.STATE_OFF
        
        self.canvas = None
        self.vadjust = None
        self.hadjust = None
        
        self.vscale = 1.0
        self.hscale = 1.0
        
        self.xReference = 0.0
        self.yReference = 0.0
        
        self.panButton = panButton
        
        # These will be used to allow disconnection
        self.buttonPressHandleId = None
        self.buttonReleaseHandleId = None
        self.mouseMovementHandleId = None
        
        self.defaultCursor = None
        
    def connect(self,
                canvas=None, vadjust=None, hadjust=None,
                vscale=1.0, hscale=1.0, scrolledWindow = None):
        '''Connects this pan controller to the reference Canvas and to the
           Adjustments vadjust and hadjust. Optionally, instead of a vadjust and
           a hadjust a scrolledWindow may be passed.'''

        if scrolledWindow != None:
            if vadjust != None or hadjust != None:
                raise Exception('If scrolledWindow is set, vadjust and '
                                'hadjust should not be set')
            else:
                vadjust = scrolledWindow.get_vadjustment()
                hadjust = scrolledWindow.get_hadjustment()

        self.canvas = canvas
        self.vadjust = vadjust
        self.hadjust = hadjust
        self.vscale = vscale
        self.hscale = hscale
        
        self.canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) 
        self.canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK) 
        self.canvas.add_events(gtk.gdk.POINTER_MOTION_MASK)
        self.canvas.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK) # This
                                                                 # magically
                                                                 # makes panning
                                                                 # smoother
        
        self.enterOff()
        
    def disconnect(self):
        '''Completely disconnects this controller from all event handlers.'''
        
        # Simply disconnects all the event handlers
        if self.buttonPressHandleId != None:
            self.canvas.disconnect(self.buttonPressHandleId)
            self.buttonPressHandleId = None
        if self.buttonReleaseHandleId != None:
            self.canvas.disconnect(self.buttonReleaseHandleId)
            self.buttonReleaseHandleId = None
        if self.mouseMovementHandleId != None:
            self.canvas.disconnect(self.mouseMovementHandleId)
            self.mouseMovementHandleId = None
    
    # The following methods set up the state
    def enterOff(self):
        '''Sets up the OFF state.'''
        
#        log.debug('Setting up OFF state')
        
        self.disconnect()
        self.buttonPressHandleId = self.canvas.connect('button-press-event',
                                                       self.handleButtonPress)
        self.state = self.STATE_OFF
    
    def enterOn(self, x, y):
        '''Sets up the ON state '''
        
        if self.state != self.STATE_ON:
#            log.debug('Setting up ON state')
            
            self.disconnect()
            
            self.xReference = x
            self.yReference = y
            
            # Saves the current cursor type and sets the new one
            window = self.canvas.get_window()
            self.defaultCursor = window.get_cursor()
            
            cursor = gtk.gdk.Cursor(gtk.gdk.FLEUR)
            window.set_cursor(cursor)
            
            # Now sets the new event handlers
            self.buttonReleaseHandleId = self.canvas.connect('button-release-event',
                                                             self.handleButtonRelease)
            self.mouseMovementHandleId = self.canvas.connect('motion_notify_event',
                                                             self.handleMouseMovement)
            
            self.state = self.STATE_ON
    
    def leaveOn(self):
        if self.state == self.STATE_ON:
#            log.debug('Leaving ON state')
            self.disconnect()
            
            # Sets the current cursor type as the old one
            window = self.canvas.get_window()
            window.set_cursor(self.defaultCursor)
    
    # The following methods handle events. They just check the event and call
    #  the correct state setting method
    def handleButtonPress(self, widget, event):
#        message = ('\n    Source: %s\n    Event: %s' % (repr(widget), repr(event)))
#        log.debug(message)
       
        if event.button == self.panButton and self.state == self.STATE_OFF:
            self.enterOn(event.x, event.y)

    def handleButtonRelease(self, widget, event):
#        message = ('\n    Source: %s\n    Event: %s' % (repr(widget), repr(event)))
#        log.debug(message)
        
        if event.button == self.panButton and self.state == self.STATE_ON:
            self.leaveOn()
            self.enterOff()

    def handleMouseMovement(self, widget, event):
#        message = ('\n    Source: %s\n    Event: %s' % (repr(widget), repr(event)))
#        log.debug(message)
        
        if self.state == self.STATE_ON:
            
            x = event.x
            y = event.y
            
            deltaX = (x - self.xReference)*self.hscale
            deltaY = (y - self.yReference)*self.vscale

            xHitLimit = False
            yHitLimit = False
            if abs(deltaX) > 0:
                xHitLimit = self.incrementAdjustment(self.hadjust, -deltaX)
            if abs(deltaY) > 0:
                yHitLimit = self.incrementAdjustment(self.vadjust, -deltaY)

            # If we hit the limit, we have to reset the references or we risk
            # spurious movements of the canvas
            if xHitLimit or yHitLimit:
                x, y = widget.get_pointer()
                if xHitLimit:
                    self.xReference = x
                if yHitLimit:
                    self.yReference = y
            
    # The following functions are ancillary only
    def incrementAdjustment(self, adjust, increment):
        '''Simply sets the adjustment to a new value, adding 'increment' to it.
        '''

        value = adjust.get_value()
        value += increment
        
        lower = adjust.get_lower()
        upper = adjust.get_upper() - adjust.get_page_size()

        if value >= lower and value <= upper:
            adjust.set_value(value)
            return False
        elif value >= lower and value - upper < increment:
            adjust.set_value(upper)
            return True
        else:
            return True
            

